home *** CD-ROM | disk | FTP | other *** search
/ NetNews Offline 2 / NetNews Offline Volume 2.iso / news / comp / std / c++ / 455 < prev    next >
Encoding:
Text File  |  1996-08-06  |  15.5 KB  |  416 lines

  1. Path: chronicle.mti.sgi.com!austern
  2. From: "ian (i.) willmott" <willmott@bnr.ca>
  3. Newsgroups: comp.std.c++
  4. Subject: Generic Object Callbacks
  5. Date: 22 Feb 1996 15:30:31 PST
  6. Organization: -
  7. Approved: austern@isolde.mti.sgi.com
  8. Message-ID: <pgpmoose.199602221531.14635@isolde.mti.sgi.com>
  9. NNTP-Posting-Host: isolde.mti.sgi.com
  10. X-Original-Date:  Tue, 20 Feb 1996 01:37:00 -0500 
  11. Content-Identifier:  Generic Objec... 
  12. X-Auth: PGPMoose V1.1 PGP comp.std.c++
  13.     iQBVAwUBMSz8uky4NqrwXLNJAQHELQH/fmc4KooMLMLXV2O9kZhZhH8xIa+UYwhU
  14.     oQT0C8j+A2Xi+BeOjTwu6fRmfc0d3NkYKrO7f0vXsaEfEY7Apk74lQ==
  15.     =zEk4
  16. Originator: austern@isolde.mti.sgi.com
  17.  
  18. Subject: Generic Object Callbacks
  19.  
  20. In an article entitled "Q: Generic Callbacks -- Object->*func(...)"
  21. (reference <4fti32$p3p@bcarh8ab.bnr.ca>), bcwhite@bnr.ca asks
  22.  
  23. "Does the C++ standard allow for a generic callback to be specified?
  24. Basically, I'd like to be able to pass an arbitrary object and
  25. function of that object to be called at some later time."
  26.  
  27. What is needed is the ability to use member functions as callbacks
  28. without any constraint on the type of the object they are invoked on.
  29. This is possible in C++ only by using various implementation-dependent
  30. hacks to escape the type system, because the language does not provide
  31. any way to express such a construct.
  32.  
  33. This is strange, because the use of callbacks is critical to
  34. event-driven programming for applications such as embedded systems and
  35. graphical interfaces; C++, which aims to support object-oriented
  36. programming, ought to allow member functions as well as ordinary
  37. functions to be the target of a callback. As yet, it doesn't.
  38.  
  39. I suggest that a new data type needs to be added to the language. We
  40. could call it "pointer-to-bound-member-function". A small example will
  41. clarify the requirement:
  42.  
  43. int *pi;
  44. int (*pf)(int);
  45.  
  46. void f1(int i)
  47. {
  48.     *pi=i;
  49.     (*pf)(i);
  50. }
  51.  
  52. // somewhere else:
  53.  
  54. class A {
  55. public:
  56.     int x;
  57.     int f(int);
  58. };
  59.  
  60. void f2(A *pa)
  61. {
  62.     pi=&pa->x;  // this is legal
  63.     pf=&pa->f;  // this isn't
  64. }
  65.  
  66. The desired semantics here are perfectly clear. Assuming f2() has been
  67. executed first, the second line in f1() should invoke A::f() on the
  68. object referenced by f2's argument, without having to know anything
  69. about the object type. This is exactly analogous with the integer
  70. pointer pi, which is why I included it.
  71.  
  72. The reason this isn't legal is because of the way function pointers
  73. are implemented, as a single machine pointer into code space. To make
  74. the example work, a pointer into data space, to identify the object,
  75. would have to be saved as well. It would be a bad idea to burden the
  76. implementation of function pointers with an extra word of storage just
  77. to make this work, so a new type is needed to provide this facility.
  78.  
  79. The name "pointer to bound member function" is suggested by the error
  80. message Cfront produces when you feed it the code above. Specifically,
  81. the type I am proposing will encapsulate the idea of a member function
  82. callback in a completely typesafe way, and will be trivially easy to
  83. implement. Conceptually, it is a two-element structure consisting of
  84. pointer into data space, identifying an object, and a pointer into
  85. code space, identifying a member function on that object. The static
  86. type of such a variable is the signature (return type and arguments)
  87. of the member functions it is compatible with, just as the type of a
  88. regular function pointer is the signature of the functions it can
  89. reference. The only operations defined for this type are assignment,
  90. equality, and callthrough. Static typechecking is done at the point
  91. where a value of this type is created, and where a call is made
  92. through it.
  93.  
  94. Notice that this type is already implicit in the language. In the
  95. expression
  96.  
  97.     (pa->f)(17)
  98.  
  99. what is the type of the subexpression (pa->f) ? The parentheses are
  100. not even necessary; the operator associativity produces the same
  101. binding. This means that C++ contains legal expressions whose type
  102. cannot be expressed in the language. I propose that the language be
  103. extended so that this type can be expressed, variables of this type
  104. can be declared, and the obvious operations on them provided.
  105.  
  106. This is what is needed:
  107. 1 - declaration syntax for objects of this type
  108. 2 - expressions such as the one above are values of this type
  109. 3 - assignment operator for variables of this type
  110. 4 - call through
  111. 5 - implicit conversion from zero
  112. 6 - implicit conversion from signature-compatible pointers-to-function
  113. 7 - equality and inequality operators for values of this type
  114.  
  115.  
  116. Declaration Syntax
  117.  
  118. No new keywords are required. A slight modification of the existing
  119. pointer-to-member-function declarator syntax will suffice.
  120.  
  121. return-type (class::*pbmf)(arg-decl-list)
  122.  
  123. declares the identifier pbmf to be a pointer-to-bound-member-function
  124. with the specified signature. "struct" should probably be allowed in
  125. place of "class". Similar syntax will be used to declare pointers and
  126. references to objects of this type, arrays of this type, functions
  127. returning this type, function arguments of this type, etc.
  128.  
  129. If this syntax is inappropriate for some reason, something similar
  130. could be devised instead.
  131.  
  132.  
  133. Values of Type Pointer-to-Bound-Member-Function
  134.  
  135. Given the declaration of class A above, and the following declarations
  136.  
  137. A obj,&ref,*ptr;
  138. int (A::*pmf)(int);
  139.  
  140. the following expressions all have type
  141. "pointer-to-bound-member-function of one int parameter returning int":
  142.  
  143. obj.f
  144. ref.f
  145. ptr->f
  146. obj.*pmf
  147. ref.*pmf
  148. ptr->*pmf
  149.  
  150. Anywhere it is possible to call a non-static class member function on
  151. an object, it is possible to compute two things: the entry point of
  152. the actual function to be executed, and the object pointer to be
  153. passed to that function. Computing the function entry point may
  154. involve virtual function table lookup, and in the presence of multiple
  155. inheritance the object pointer passed to the function may not be the
  156. same as the address of the object on which the function was called.
  157. The pair of these values constitute a value of type
  158. pointer-to-bound-member-function. This value can be used immediately
  159. to specify a member function call, or it can be saved for later use.
  160. If this happens, information about the type of the object need not be
  161. preserved, because all typechecking except of the actual parameters
  162. used in the call has already been done.
  163.  
  164. If an expression of one of the first three forms above refers to a
  165. static member function, the data-pointer component of the resulting
  166. pointer-to-bound-member-function will have the value used by the
  167. implementation to represent a null pointer.
  168.  
  169.  
  170. Assignment
  171.  
  172. Assignment to an lvalue of type pointer-to-bound-member-function is
  173. defined to mean copying of the two components of the value on the
  174. right-hand-side of the assignment expression into the corresponding
  175. elements of the lvalue.
  176.  
  177.  
  178. Call Through
  179.  
  180. If the identifier pbmf denotes a pointer-to-bound-member-function, and
  181. the arguments supplied in an expression of the form
  182.  
  183. (*pbmf)(arg-list)
  184.  
  185. match the function signature of pbmf, then the expression specifies a
  186. function call through the value contained in pbmf, the type of the
  187. expression is the return type of pbmf, and the value of the expression
  188. is the value returned by the called function.
  189.  
  190. If the data-pointer component of the value of pbmf is non-null, it
  191. will be passed to the called function using the usual mechanism for
  192. object-pointers in member function calls; if it is null, it will not
  193. be. In this most implementations, this will mean that it will be
  194. pushed onto the stack after all the other parameters only if it is
  195. non-zero. The code-pointer component of the value of pbmf specifies
  196. the exact entry point of the function to be executed. If it is null,
  197. the resulting behaviour is undefined.
  198.  
  199. In order to match the syntax for calls through regular
  200. pointers-to-function, the expression
  201.  
  202. pbmf(arg-list)
  203.  
  204. should be allowed and have the same meaning.
  205.  
  206.  
  207. Implicit Conversions
  208.  
  209. A value of type pointer-to-function can be implicitly converted to a
  210. pointer-to-bound-member-function of the same signature. The
  211. code-pointer component of the resulting PBMF has the same value as the
  212. pointer-to-function, and the data-pointer component of the PBMF has
  213. whatever value the implementation uses to represent null data
  214. pointers. This implies that there is an implicit conversion to PBMF
  215. from zero, since zero can be implicitly converted to
  216. pointer-to-function.
  217.  
  218.  
  219. Equality and Inequality Operators
  220.  
  221. Two values of the same pointer-to-bound-member-function type may be
  222. compared to each other using the == and != operators. This is defined
  223. to mean component-wise comparison of the two values, which are
  224. considered to be equal only if the corresponding components are both
  225. equal, and not equal otherwise.
  226.  
  227.  
  228. General Comments
  229.  
  230. For reasons of syntactic consistency, declarations such as the
  231. following should probably be allowed:
  232.  
  233. int class::*pbmd;
  234.  
  235. Objects declared this way will have the same semantics and
  236. implementation as normal data pointers and there should be implicit
  237. conversions in both directions between them.
  238.  
  239. PBMFs are upward compatible with regular pointers-to-function;
  240. anywhere a callback might have to refer to member function on an
  241. object, it should be declared as a PBMF; either type can then be
  242. assigned to it, and the callthrough will behave appropriately. Where
  243. this facility is not needed, pointers-to-function will suffice.
  244.  
  245. The proposed type pointer-to-bound-member-function introduces to C++
  246. no new keywords, only one new piece of syntax, the declarator, and one
  247. new run-time operation, the callthrough, consisting of a conditional
  248. stack push and a function call. It is statically type-checked in
  249. exactly the same way as a regular member function call; it merely
  250. separates member function lookup and invocation. Objects of this type
  251. are opaque; there is no way to access the two components independently
  252. and consequently it is completely type-safe, something that cannot be
  253. said for the kludges currently necessary to implement the
  254. functionality it provides. This datatype can be implemented in two
  255. machine words on linear address-space architectures and requires no
  256. additional run-time support. It appears to be completely orthogonal to
  257. the rest of the C++ language.
  258.  
  259.  
  260. Liabilities
  261.  
  262. I can think of only one possible problem. If a PBMF referring to a
  263. virtual function is exported from a base class constructor invoked for
  264. a derived class object, then when a call through that PBMF is made,
  265. the member function invoked will be the one defined for the base class
  266. and not the derived class. No simple implementation can prevent this
  267. possibility. It's pretty hard to think of any good reason why you
  268. would want to do this. This is analogous to the situation of calling a
  269. pure virtual function from a base class constructor, although at least
  270. that can be detected at runtime. There is the corresponding
  271. possibility of importing a PBMF into a base class destructor, but
  272. that's even more unlikely. Any function pointers used in code called
  273. from constructors and destructors would be much better expressed as
  274. pointers-to-member-function.
  275.  
  276.  
  277. Possible Objections
  278.  
  279.  
  280. "You can do this with an object pointer and a pointer-to-member-function."
  281.  
  282. Only if all of your callbacks are to objects in the same class
  283. hierarchy (a severe restriction), and to members declared in exactly
  284. the same class.  It's not even good enough that they be declared in
  285. classes derived from a common base type, since if D is derived from B,
  286. you can assign an value of type (B::*)() to an object of type
  287. (D::*)(), but not the other way around. There's a good reason for this
  288. rule: if it didn't exist it would be possible to call a member
  289. function on an object of a class for which it was not defined.
  290.  
  291. This points out the need for a type which encapsulates an object
  292. reference and a member function reference so that there is no
  293. possibility of applying the function reference to an object of
  294. inappropriate type.
  295.  
  296.  
  297. "There are other ways to do this."
  298.  
  299. There are two that I know of.
  300.  
  301. class pbmf {
  302.     void *ptr;
  303.     int (*func)(void *,int)
  304. public:
  305.     pbmf(void *p,int (*f)(void *,int)):
  306.         ptr(p),func(f) {}
  307.     int operator()(int x)
  308.         { return(func(ptr,x)); }
  309. };
  310.  
  311. int Af(void *p,int x)
  312. {
  313.     return(((A *) p)->f(x));
  314. }
  315.  
  316. A obj;
  317. pbmf pf(&obj,&Af);
  318.  
  319. pf(17);
  320.  
  321. This isn't typesafe, it requires you to rewrite the class declaration
  322. for every new function signature, and to write a wrapper function
  323. around every member function you want to use this way, and imposes the
  324. overhead of an extra function call.
  325.  
  326. class pbmfbase {
  327. public:
  328.     virtual int operator()(int);
  329. };
  330.  
  331. template<class T> class pbmf: public pbmfbase {
  332.     T *ptr;
  333.     int (T::*func)(int);
  334. public:
  335.     pbmf(T *p,int (T::*f)(int))
  336.         ptr(p),func(f) {}
  337.     int operator()(int x)
  338.         { return(ptr->*func(x)); }
  339. };
  340.  
  341. pbmfbase *pf=new pbmf<A>(&obj,&A::f);
  342.  
  343. (*pf)(17);
  344.  
  345. This requires a new template for every different function signature
  346. (you might be able to do something with member function templates), it
  347. costs an extra virtual function call, and because the callback has to
  348. be done through a pointer or reference, objects of this type are
  349. probably going to end up on the heap, although you might be able to
  350. avoid this with some other type-unsafe tricks. Using templates is
  351. really overkill for an application this simple.
  352.  
  353. These are really ugly hacks to accomplish something that should be
  354. directly supported by the language. The fact that the type
  355. pointer-to-bound-member-function is implicit but unexpressable in the
  356. language shows that this is a real deficiency.
  357.  
  358.  
  359. "Any built-in type should fit in a single processor register."
  360.  
  361. According to the discussion in the ARM and the D&E, pointers to member
  362. function are represented by structures with at least three elements.
  363. PBMFs are much easier to implement than pointers-to-member-function.
  364.  
  365. Also, floating point numbers often do not fit into a single register on
  366. machines without hardware floating point support.
  367.  
  368.  
  369. "What will happen if a PBMF is called through after the object it
  370. refers to has been destroyed?"
  371.  
  372. This is the same situation as calling a member function through a
  373. regular pointer which does not reference a valid object, either
  374. because the pointer is invalid, or the object has been destroyed. The
  375. language does not prevent you from doing this.
  376.  
  377.  
  378. "It's not type-safe."
  379.  
  380. PBMFs are statically type-checked in exactly the same way that regular
  381. member function calls are. They simply separate the object
  382. type-checking from function invocation. Once member function lookup
  383. and pointer adjustment are done, there is no further need for object
  384. type information.
  385.  
  386.  
  387. I realize that the probability of any more extensions to the standard
  388. being accepted at this point is small, but I thought that it would be
  389. worth discussing this one anyway. I would not be at all surprised to
  390. hear that something similar to this has been proposed before, although
  391. I saw no mention of any such in the D&E. If so, I would like to know
  392. why it was rejected.
  393.  
  394. I would like to know how many people think that this would be a useful
  395. language feature. It seems to me that it would make object-oriented,
  396. event-driven programming much easier. I have been working on the
  397. embedded control software for a network switch for the last two years
  398. and the need for this facility has arisen over and over again, but I
  399. have had to make do with the sort of kludges listed above. When the
  400. GNU C++ compiler becomes more stable, I will look into adding this
  401. datatype to it. All comments are welcome.
  402.  
  403.  
  404. Ian Willmott
  405. Bell-Northern Research
  406. Ottawa, Ontario
  407. (613)-763-9688
  408. willmott@bnr.ca
  409. ---
  410. [ To submit articles: Try just posting with your newsreader.  If that fails,
  411.                       use mailto:std-c++@ncar.ucar.edu
  412.   FAQ:    http://reality.sgi.com/employees/austern_mti/std-c++/faq.html
  413.   Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
  414.   Comments? mailto:std-c++-request@ncar.ucar.edu 
  415. ]
  416.